#include "lvgl/lvgl.h"
#include <stdio.h>
#include "config/CustomConfig.h"
#include "include/common.h"
#include "src/home/home.h"
#include "utils/components/navigation_bar.h"
#include "assets/fonts/single_color_emoji.h"
#include "utils/components/sdl_disp_window.h"
#include <cjson/cJSON.h>
#include "utils/network/json_analysis.h"
#include "utils/network/http_client.h"
#include <stdio.h>
#include <string.h>
#include "utils/str_util.h"
#include "utils/file_util/file_util.h"
#include "utils/qrcode/qrcode_util.h"
#include "assets/fonts/chinese/lv_font_sans_sc_14.h"
#include "models/bd_structs.h"
#include "utils/components/lv_obj_util.h"

// 百度网盘开放平台协议地址：https://openauth.baidu.com/doc/doc.html
// 协议文档：https://pan.baidu.com/union/doc/fl1x114ti

// 外部变量声明（extern）不会分配任何内存，等价于：extern const lv_img_dsc_t back;
// lv_obj_t *img = lv_img_create(lv_scr_act());才会在运行时从堆上分配一个 LVGL 控件对象 的堆内存
// lv_img_set_src(img, &back);&back 指向的内存是编译时固定的常量地址
LV_IMG_DECLARE(back);
lv_obj_t * bdwp_openapi_screen;
lv_obj_t *qrcode_canvas;

DeviceAuthResponse g_device_auth = {0};//get_code函数获取设备码响应数据
UserInfo user_info = {0}; // 全局变量，程序运行期间使用
OAuthToken g_oauth_token = {0}; // 全局变量，程序运行期间使用
StorageInfo storage_info = {0}; // 全局变量，程序运行期间使用
int g_poll_count = 0;


const char *upload_file_base_url = "https://c3.pcs.baidu.com/rest/2.0/pcs/file";
// const char *upload_file_base_url = "https://xafj-cm11.pcs.baidu.com";
const char *file_base_url = "https://pan.baidu.com/rest/2.0/xpan/file";
const char *get_info_base_url = "https://openapi.baidu.com/rest/2.0/passport/users/getInfo";
const char *get_quota_base_url = "https://pan.baidu.com/api/quota";
const char *get_token_base_url = "https://openapi.baidu.com/oauth/2.0/token";
const char *get_code_base_url = "https://openapi.baidu.com/oauth/2.0/device/code";
// char *client_id = getenv("BDWP_AppKey");
// 编译报错❌：常量表达式中不允许函数调用C/C++(59)
// 原因分析✅：getenv是运行时函数，只有程序运行后才能返回环境变量值，而全局常量的初始化发生在编译期即程序加载时的编译时常量
// 解决方法✅：放在函数体中执行该运行时函数，即全局变量变更为函数作用域内的局部变量

#define ITEM_COUNT 11
static HomeItem items[ITEM_COUNT] = {
    {"🏠", "设备码模式授权"},
    {"📁", "授权码授权回调redirect_uri"},
    {"📁", "获取授权信息"},
    {"❓", "获取授权用户信息"},
    {"⚙️", "获取网盘容量信息"},
    {"⚙️", "创建文件夹"},
    {"⚙️", "删除文件"},
    {"⚙️", "文件重命名"},
    {"⚙️", "移动文件"},
    {"⚙️", "复制文件"},
    {"⚙️", "上传文件"},
};

/**
 * 上传文件
 * @param bdwp_file_path 上传到网盘的存放路径："/apps/车多多/sound.jpg"，
 * 或者UrlEncode编码都行："%2Fapps%2F%E8%BD%A6%E5%A4%9A%E5%A4%9A%2Fsound.jpg"
 * @param file_name 上传文件字段名称："file"或者"image"
 * @param file_path 上传文件本地路径："D:/Downloads/filename.jpg"
 *
 * @return _Bool 删除文件是否成功
 */
_Bool upload_file(char *bdwp_file_path, char *file_name, char *file_path) {
    if (is_empty_str(g_oauth_token.access_token))
    {
        printf("upload file failed without access_token\n");
        return true;
    }

    // ✅upload_file_base_url上传文件域名通过下面的接口获取，如下：
    // https://d.pcs.baidu.com/rest/2.0/pcs/file
    // URL参数：
    // method=locateupload
    // appid=250528
    // access_token=126.c01e10f773e89c7623267b527cf491f7.YG4MW3wEYAY6gAZQG_y2_AQSvtJfD2655unQb3w.W6TGOg
    // path=%2Fapps%2F%E8%BD%A6%E5%A4%9A%E5%A4%9A%2Ffilename.jpg
    // uploadid=sddasdad
    // upload_version=2.0

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?method=upload&access_token=%s&path=%s",
        upload_file_base_url, g_oauth_token.access_token, bdwp_file_path);
    cJSON *json = http_post_upload_file_req(url, file_name, file_path, "pan.baidu.com");

    char *json_string = json_to_string(json);
    int errno = getCJSONInt(json, "errno");
    if (errno != 0)
    {
        // ✅errno为2可能是Post请求写成Get请求，切换下method即可
        // 也可能是参数缺失，检查必选参数是否都已填写，检查参数位置，有的参数是在url里，有的是在body里，检查每个参数的值是否正确；
        printf("upload file failed: %s, url: %s\n", json_string, url);
        return false;
    }
    printf("upload file result: %s, url: %s\n", json_string, url);
    //upload file result: {"error_code":31064,"error_msg":"file is not authorized","request_id":7.2013544400010158e+18}, url: https://c3.pcs.baidu.com/rest/2.0/pcs/file?method=upload&access_token=126.c01e10f773e89c7623267b527cf491f7.YG4MW3wEYAY6gAZQG_y2_AQSvtJfD2655unQb3w.W6TGOg&path=/WORK/bdwp_api_test/sound6.png
    //upload file result: 403 Forbidden, url: https://xafj-cm11.pcs.baidu.com?method=upload&access_token=126.c01e10f773e89c7623267b527cf491f7.YG4MW3wEYAY6gAZQG_y2_AQSvtJfD2655unQb3w.W6TGOg&path=%2Fapps%2F%E8%BD%A6%E5%A4%9A%E5%A4%9A%2Ffilename.jpg
    //upload file result: {"ctime":1762496175,"fs_id":387487868628668,"md5":"5464ce1a4oef78ca910332edc7b9f6f6","mtime":1762496175,"path":"/apps/xe8xbdxa6xe5xa4x9axe5xa4x9a/new_cymbal.jpg","request_id":7.2035661690949222e+18,"size":32858}, url: https://c3.pcs.baidu.com/rest/2.0/pcs/file?method=upload&access_token=126.c01e10f773e89c7623267b527cf491f7.YG4MW3wEYAY6gAZQG_y2_AQSvtJfD2655unQb3w.W6TGOg&path=/apps/xe8xbdxa6xe5xa4x9axe5xa4x9a/new_cymbal.jpg
    return true;
}

/**
 * 删除文件
 * @param file_path 文件路径："/WORK/bdwp_api_test/sound.png"
 * @return _Bool 删除文件是否成功
 */
_Bool delete_file(char *file_path) {
    if (is_empty_str(g_oauth_token.access_token))
    {
        printf("delete file failed without access_token\n");
        return true;
    }

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?method=filemanager&access_token=%s&opera=delete",
        file_base_url, g_oauth_token.access_token);
    char form_urlencoded[256];
    snprintf(form_urlencoded, sizeof(form_urlencoded), "filelist=[\"%s\"]&async=1", file_path);

    cJSON *json = http_post_req(url, NULL, form_urlencoded, "pan.baidu.com");

    char *json_string = json_to_string(json);
    int errno = getCJSONInt(json, "errno");
    if (errno != 0)
    {
        // ✅errno为2可能是Post请求写成Get请求，切换下method即可
        // 也可能是参数缺失，检查必选参数是否都已填写，检查参数位置，有的参数是在url里，有的是在body里，检查每个参数的值是否正确；
        printf("delete file failed: %s, url: %s\n", json_string, url);
        return false;
    }
    printf("delete file result: %s, url: %s\n", json_string, url);
    // delete file result: {"errno":0,"info":[{"errno":0,"path":"/WORK/bdwp_api_test/sound2.png"}],"request_id":2.69661922429313e+17}, url: https://pan.baidu.com/rest/2.0/xpan/file?method=filemanager&access_token=126.c01e10f773e89c7623267b527cf491f7.YG4MW3wEYAY6gAZQG_y2_AQSvtJfD2655unQb3w.W6TGOg&opera=delete
    return true;
}

/**
 * 文件重命名
 * @param file_path 文件路径："/WORK/bdwp_api_test/sound.png"
 * @param newname 文件新名称
 * @return _Bool 文件重命名是否成功
 */
_Bool rename_file(char *file_path, char *newname) {
    if (is_empty_str(g_oauth_token.access_token))
    {
        printf("rename file failed without access_token\n");
        return true;
    }

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?method=filemanager&access_token=%s&opera=rename",
        file_base_url, g_oauth_token.access_token);
    char form_urlencoded[256];
    snprintf(form_urlencoded, sizeof(form_urlencoded), "filelist=[{\"path\":\"%s\", \"newname\":\"%s\"}]&async=1", file_path, newname);

    cJSON *json = http_post_req(url, NULL, form_urlencoded, "pan.baidu.com");

    char *json_string = json_to_string(json);
    int errno = getCJSONInt(json, "errno");
    if (errno != 0)
    {
        // ✅errno为2可能是Post请求写成Get请求，切换下method即可
        // 也可能是参数缺失，检查必选参数是否都已填写，检查参数位置，有的参数是在url里，有的是在body里，检查每个参数的值是否正确；
        printf("rename file failed: %s, url: %s\n", json_string, url);
        return false;
    }
    printf("rename file result: %s, url: %s\n", json_string, url);
    return true;
}


/**
 * 移动文件 / 复制文件
 * @param file_path 文件路径："/WORK/bdwp_api_test/sound.png"
 * @param dest_path 文件目标路径："/WORK/bdwp_api_test"
 * @param newname 文件新名称
 * @param is_copy 是否为复制文件，否则即为移动文件
 * @return _Bool 移动或复制文件是否成功
 */
_Bool move_copy_file(char *file_path, char *dest_path, char *newname, _Bool is_copy) {
    char *opera = is_copy ? "copy" : "move";//是否为复制文件，否则即为移动文件
    if (is_empty_str(g_oauth_token.access_token))
    {
        printf("%s file failed without access_token\n", opera);
        return true;
    }

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?method=filemanager&access_token=%s&opera=%s",
        file_base_url, g_oauth_token.access_token, opera);
    char form_urlencoded[256];
    snprintf(form_urlencoded, sizeof(form_urlencoded), "filelist=[{\"path\":\"%s\", \"dest\":\"%s\", \"newname\":\"%s\"}]&async=1", file_path, dest_path, newname);

    cJSON *json = http_post_req(url, NULL, form_urlencoded, "pan.baidu.com");

    char *json_string = json_to_string(json);
    int errno = getCJSONInt(json, "errno");
    if (errno != 0)
    {
        // ✅errno为2可能是Post请求写成Get请求，切换下method即可
        // 也可能是参数缺失，检查必选参数是否都已填写，检查参数位置，有的参数是在url里，有的是在body里，检查每个参数的值是否正确；
        printf("%s file failed: %s, url: %s\n", opera, json_string, url);
        return false;
    }
    printf("%s file result: %s, url: %s\n", opera, json_string, url);
    // move file result: {"errno":0,"info":[{"errno":0,"path":"/WORK/bdwp_api_test/sound3.png","to_path":"/WORK/bdwp_api_test/bdwp_api_sub_dir/sound4.png"}],"request_id":2.70237404886707e+17}, url: https://pan.baidu.com/rest/2.0/xpan/file?method=filemanager&access_token=126.c01e10f773e89c7623267b527cf491f7.YG4MW3wEYAY6gAZQG_y2_AQSvtJfD2655unQb3w.W6TGOg&opera=move
    // copy file result: {"errno":0,"info":[{"errno":0,"path":"/WORK/bdwp_api_test/bdwp_api_sub_dir/sound4.png","to_fs_id":435540443943097,"to_path":"/WORK/bdwp_api_test/sound5.png"}],"request_id":2.70317322965106e+17}, url: https://pan.baidu.com/rest/2.0/xpan/file?method=filemanager&access_token=126.c01e10f773e89c7623267b527cf491f7.YG4MW3wEYAY6gAZQG_y2_AQSvtJfD2655unQb3w.W6TGOg&opera=copy
    return true;
}

/**
 * 创建文件夹
 * @param dir_path 文件夹路径："/WORK/bdwp_api_test"
 * @return _Bool 创建文件夹是否成功
 */
_Bool create_dir(char *dir_path) {
    if (is_empty_str(g_oauth_token.access_token))
    {
        printf("get storage info failed without access_token\n");
        return true;
    }

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?method=create&access_token=%s&checkfree=1&checkexpire=1",
        file_base_url, g_oauth_token.access_token);
    char post_fields[256];
    snprintf(post_fields, sizeof(post_fields), "path=%s&isdir=1&rtype=0", dir_path);

    cJSON *json = http_post_req(url, NULL, post_fields, "pan.baidu.com");

    char *json_string = json_to_string(json);
    int errno = getCJSONInt(json, "errno");
    if (errno != 0)
    {
        printf("create dir failed: %s, url: %s\n", json_string, url);
        return false;
    }
    printf("create dir result: %s, url: %s\n", json_string, url);
    //create dir result: {"ctime":1762405685,"fs_id":54458298953604,"isdir":1,"mtime":1762405685,"path":"/WORK/bdwp_api_test","status":0,"errno":0,"name":"/WORK/bdwp_api_test","category":6}
    return true;
}

//获取网盘容量信息
_Bool get_storage_info() {
    cJSON * get_storage_info_json = read_json_from_file(BD_STORAGE_INFO_JSON_PATH);
    if (get_storage_info_json)
    {
        config_storage_info_rep(&storage_info, get_storage_info_json);
        if (storage_info.total_GB > 0)
        {
            printf("get storage info json read from file: %s\n", json_to_string(get_storage_info_json));
            printf("Total space size: %f GB, Free size: %f GB, Used size: %f GB, expire: %s\n",
                storage_info.total_GB,
                storage_info.free_GB,
                storage_info.used_GB,
                storage_info.expire ? "true" : "false");
            return true;
        }
    }

    if (is_empty_str(g_oauth_token.access_token))
    {
        printf("get storage info failed without access_token\n");
        return true;
    }

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?access_token=%s&checkfree=1&checkexpire=1",
        get_quota_base_url, g_oauth_token.access_token);

    cJSON *json = http_get_req(url, NULL, "pan.baidu.com");

    char *json_string = json_to_string(json);

    //get info result: {"portrait":"88ac436865636b5f4a469935","username":"C***F","is_bind_mobile":"1","is_realname":"1","birthday":"0000-00-00","sex":"1","openid":"oPsAi-xB5dS2MXViilBXesyZSbcwh98"}
    save_json_to_file(BD_STORAGE_INFO_JSON_PATH, json);
    config_storage_info_rep(&storage_info, json);
    int errno = getCJSONInt(json, "errno");
    if (errno != 0)
    {
        printf("get storage info failed: %s, url: %s\n", json_string, url);
        return false;
    }
    printf("get storage info result: %s, url: %s\n", json_string, url);
    printf("Total space size: %f GB, Free size: %f GB, Used size: %f GB, expire: %s\n",
        storage_info.total_GB,
        storage_info.free_GB,
        storage_info.used_GB,
        storage_info.expire ? "true" : "false");
    return true;
}

// 通过access_token获取授权用户信息
_Bool get_info() {
    cJSON * get_info_json = read_json_from_file(BD_INFO_JSON_PATH);
    if (get_info_json)
    {
        config_info_rsp(&user_info, get_info_json);
        if (!is_empty_str(user_info.openid))
        {
            printf("get access token json read from file: %s\n", json_to_string(get_info_json));
            return true;
        }
    }

    if (is_empty_str(g_oauth_token.access_token))
    {
        printf("get info failed without access_token\n");
        return true;
    }

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?access_token=%s",
        get_info_base_url, g_oauth_token.access_token);

    cJSON *json = http_get_req(url, NULL, "pan.baidu.com");

    char *json_string = json_to_string(json);
    printf("get info result: %s\n", json_string);
    //get info result: {"portrait":"88ac436865636b5f4a469935","username":"C***F","is_bind_mobile":"1","is_realname":"1","birthday":"0000-00-00","sex":"1","openid":"oPsAi-xB5dS2MXViilBXesyZSbcwh98"}
    save_json_to_file(BD_INFO_JSON_PATH, json);
    config_info_rsp(&user_info, json);
    if (is_empty_str(user_info.openid))
    {
        return false;
    }
    return true;
}

// 通过百度网盘device code获取AccessToken
_Bool get_access_token_with_device_code() {
    cJSON * get_access_token_json = read_json_from_file(BD_ACCESS_TOKEN_JSON_PATH);
    if (get_access_token_json)
    {
        config_access_token_rsp(&g_oauth_token, get_access_token_json);
        if (!is_empty_str(g_oauth_token.access_token))
        {
            printf("get access token json read from file: %s\n", json_to_string(get_access_token_json));
            return true;
        }
    }


    const char *client_id = getenv("BDWP_AppKey");
    const char *client_secret = getenv("BDWP_Secretkey");

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?grant_type=device_token&code=%s"
        "&client_id=%s"
        "&client_secret=%s",
        get_token_base_url, g_device_auth.device_code, client_id, client_secret);

    cJSON *json = http_get_req(url, NULL, "pan.baidu.com");

    char *json_string = json_to_string(json);
    printf("get access token result: %s\n", json_string);
    //get access token result: {"error":"authorization_pending","error_description":"User has not yet completed the authorization"}
    //get access token result: {"expires_in":2592000,"refresh_token":"127.fc50c4797fd4cba49827ae6ec30abde9.Y7AKfFBQ1NdOOsS2xWxRihBJXZWrkMtNF1CJEYp.dhMmTQ","access_token":"126.c01e10f773e89c7623267b527cf491f7.YG4MW3wEYAY6gAZQG_y2_AQSvtJfD2655unQb3w.W6TGOg","session_secret":"","session_key":"","scope":"basic netdisk"}
    //✅expires_in为Access Token的有效期，单位为秒，2592000秒对应720小时即30天。
    save_json_to_file(BD_ACCESS_TOKEN_JSON_PATH, json);
    config_access_token_rsp(&g_oauth_token, json);
    if (is_empty_str(g_oauth_token.access_token))
    {
        return false;
    }
    //用户授权成功，移除授权二维码
    if (qrcode_canvas) {
        remove_from_surperview(qrcode_canvas);
    }
    return true;
}

// 每 interval 秒轮询一次
static void poll_access_token_cb(lv_timer_t *timer) {
    (void)timer;

    g_poll_count++;

    // 最大轮询次数
    int max_poll = g_device_auth.expires_in / g_device_auth.interval;
    if (g_poll_count > max_poll) {
        printf("Device authorization expired\n");
        lv_timer_del(timer);
        return;
    }

    printf("Polling get_access_token(), attempt %d\n", g_poll_count);

    bool success = get_access_token_with_device_code();
    if (success && !is_empty_str(g_oauth_token.access_token)) {
        printf("Got access token: %s\n", g_oauth_token.access_token);
        lv_timer_del(timer); // 获取access token成功，停止轮询
    }
}

void config_qr_code() {
    printf("verification_url: %s?display=mobile&code=%s，device_code: %s，user_code: %s\n", g_device_auth.verification_url, g_device_auth.user_code, g_device_auth.device_code, g_device_auth.user_code);
    char *veri_url = build_string("%s?display=mobile&code=%s", g_device_auth.verification_url, g_device_auth.user_code);
    qrcode_canvas = create_qrcode(bdwp_openapi_screen, veri_url);
    free(veri_url);
    lv_obj_center(qrcode_canvas);
}

/**
 * 获取设备码device_code、用户码user_code、授权verification_url，并通过下面方式得到给用户扫码授权的url生成二维码给用户扫码授权
 * 显示二维码后开始每隔至少5秒轮询一次(具体几秒参考协议文档)，授权成功后即可通过设备码device_code获取Access Token
 * refresh_token 只支持使用一次，refresh_token 使用后失效，下次刷新 Access Token 时需要使用上一次刷新请求响应中的 refresh_token。
 * 用户授权有两种方式：一种是扫描二维码方式，另一种是输入用户码方式。关于用户码，在开发者获取设备码的同时，会返回用户码。
 *
    char url[1024];
    snprintf(url, sizeof(url), "%s?display=mobile&code=%s", verification_url, user_code)

 * 注：二维码内容拼接规则https://openapi.baidu.com/device?display=mobile&code=u6rgnvp4，
 * 其中api地址为返回的verification_url，参数code为接口返回内容的user_code字段 
 * @param void
 * @return void
 */
static void get_code() {
    // 读取get code文件存储数据
    // ❌由于expires_in有效时长是300秒即5分钟，正常第二次启动基本都失效了，因此不做文件存储数据持久化
    // cJSON * get_code_json = read_json_from_file(BD_CODE_JSON_PATH);
    // if (get_code_json)
    // {
    //     config_device_auth_rep(&g_device_auth, get_code_json);
    //     config_qr_code();
    //     lv_timer_create(poll_access_token_cb, g_device_auth.interval * 1000, NULL);
    //     printf("get code json read from file: %s\n", json_to_string(get_code_json));
    //     return;
    // }

    const char *client_id = getenv("BDWP_AppKey");//通过环境变量或配置文件读取
    char url[1024];
    snprintf(url, sizeof(url),
        "%s?response_type=device_code"
        "&client_id=%s"
        "&scope=basic,netdisk",
        get_code_base_url, client_id);
    cJSON *json = http_get_req(url, NULL, "pan.baidu.com");

    char *json_string = json_to_string(json);
    printf("get code result: %s\n", json_string);
    //get code result: {"device_code":"3fac9764f61d735f7a9246d1661005c3","user_code":"2ftty46z","verification_url":"https://openapi.baidu.com/device","qrcode_url":"https://openapi.baidu.com/device/qrcode/b2e8525f22b16b7fa4c0958e1d41369d/2ftty46z","expires_in":300,"interval":5}

    save_json_to_file(BD_CODE_JSON_PATH, json);
    config_device_auth_rep(&g_device_auth, json);
    config_qr_code();


    // 在获取 device_code 后启动轮询，每隔interval秒尝试获取access token直到用户授权后获取成功
    // interval 单位秒，LVGL timer单位毫秒，创建LVGL定时器，period单位毫秒
    lv_timer_create(poll_access_token_cb, g_device_auth.interval * 1000, NULL);
}

/**
 * 获取授权码Code，一次有效并在10分钟之后过期，且仅一次有效。授权码模式适用于有Server端的应用，因为授权后要回调到Server端redirect_uri地址
 * refresh_token 只支持使用一次，refresh_token 使用后失效，下次刷新 Access Token 时需要使用上一次刷新请求响应中的 refresh_token。
 * @param void
 * @return void
 */
static void baidu_openapi_authorize() {
    const char *base_url = "http://openapi.baidu.com/oauth/2.0/authorize";
    const char *client_id = getenv("BDWP_AppKey");
    const char *redirect_uri = "https://yourdomain.com/oauth/callback";

    // ✅display授权页的展示方式
    // display参数值	 参数值说明
    // page	            全屏形式的授权页面(默认)，适用于web应用
    // popup	        弹框形式的授权页面，适用于桌面软件应用和web应用
    // dialog	        浮层形式的授权页面，只能用于站内web应用
    // mobile	        Iphone/Android等智能移动终端上用的授权页面，适用于Iphone/Android等智能移动终端上的应用
    // tv	            电视等超大显示屏使用的授权页面
    // pad	            IPad/Android等智能平板电脑使用的授权页面
    const char *display = "tv";
    //让用户通过扫二维码的方式登录百度账号时，可传递 “qrcode=1”
    const int qrcode = 1;
    //当需要加载登录页时强制用户输入用户名和密码，不会从 Cookies 中读取百度用户的登录状态，传递 “force_login=1”
    const int force_login = 1;

    char url[1024];
    snprintf(url, sizeof(url),
        "%s?response_type=code"
        "&client_id=%s"
        "&redirect_uri=%s"
        "&scope=basic,netdisk"
        "&display=%s"
        "&qrcode=%d"
        "&force_login=%d",
        base_url, client_id, redirect_uri, display, qrcode, force_login);

    cJSON *json = http_get_req(url, NULL, "pan.baidu.com");

    char *result = getCJSONString(json, "result");
    printf("result: %s\n", result);
    int code = getCJSONInt(json, "code");
    printf("code: %d\n", code);
    int resultCode = getCJSONInt(json, "resultCode");
    printf("completed: %s\n", resultCode != 0 ? "true" : "false");
}

/* 点击事件回调
加上 static → 限制作用域，防止外部文件访问
*/
static void item_event_cb(lv_event_t * e)
{
    int click_index = (int)(intptr_t)lv_event_get_user_data(e);  // ✅ 从 user_data 获取索引
    const char *text = items[click_index].text;
    printf("Clicked item at: %d %s\n", click_index, text);
    switch (click_index)
    {
    case 0: {
        get_code();
        break;
    }
    case 1: {
        baidu_openapi_authorize();
        break;
    }
    case 2: {
        get_access_token_with_device_code();
        break;
    }
    case 3: {
        get_info();
        break;
    }
    case 4: {
        get_storage_info();
        break;
    }
    case 5: {
        create_dir("/WORK/bdwp_api_test");
        break;
    }
    case 6: {
        delete_file("/WORK/bdwp_api_test/sound2.png");
        break;
    }
    case 7: {
        rename_file("/WORK/bdwp_api_test/sound2.png", "sound3.png");
        break;
    }
    case 8: {
        move_copy_file("/WORK/bdwp_api_test/sound3.png", "/WORK/bdwp_api_test/bdwp_api_sub_dir", "sound4.png", false);
        break;
    }
    case 9: {
        move_copy_file("/WORK/bdwp_api_test/bdwp_api_sub_dir/sound4.png", "/WORK/bdwp_api_test", "sound5.png", true);
        break;
    }
    case 10: {
        char *file_path = "./assets/images/cymbal/cymbal_00.jpg";// ✅./assets表示根目录的assets文件夹
        if (!is_file_exist(file_path)) {
            printf("file not exist: %s\n", file_path);
            return;
        }

        char *bdwp_file_name = "new_cymbal.jpg";
        char *bdwp_file_path = build_string("/apps/车多多/%s", bdwp_file_name);
        // 或者UrlEncode编码都行
        // char *bdwp_file_path = build_string("%%2Fapps%%2F%%E8%%BD%%A6%%E5%%A4%%9A%%E5%%A4%%9A%%2F%s", bdwp_file_name);

        upload_file(bdwp_file_path, "file", file_path);
        break;
    }
    default:
        break;
    }
    // 获取项目当前LVGL版本，LVGL version: 9.4.0
    // printf("LVGL version: %d.%d.%d\n", LVGL_VERSION_MAJOR, LVGL_VERSION_MINOR, LVGL_VERSION_PATCH);
}
static void back_btn_event_handler(lv_event_t * e)
{
    printf("back_btn_event_handler\n");
    lv_screen_load(home_screen);//返回主屏幕
    set_window_title(home_disp, "Home");
    // lv_scr_load()只是将活动屏幕切换为新的screen，把旧屏幕从显示链上移除（不显示），但不会删除旧屏幕对象本身
    // 因此如果不再需要data_handle_screen必须手动释放，否则它会一直占用内存：
    lv_obj_del(bdwp_openapi_screen);

}

/* 创建 bdwp_openapi_screen 界面，负责创建 UI 组件，如按钮、滑块、标签等，即专注于界面结构和布局。
* 对于页面初始化数据另外void init_settings_page(void)：负责加载数据并更新 UI，例如从文件读取配置、设置滑块初始值、填充文本等。
*/
void bdwp_openapi_ui(void)
{
    bdwp_openapi_screen = lv_obj_create(NULL);
    // 添加导航栏
    lv_obj_t *nav = normal_nav_bar_create(bdwp_openapi_screen, "bdwp openapi", back_btn_event_handler);

    /* 创建一个滚动页面容器 */
    // 通过lv_obj_create()方法在lv_scr_act()当前活动屏幕创建一个基础对象（容器），这个容器可以包含子对象（比如按钮、标签等）。
    // 返回值(list)是一个指向这个新建对象的指针。
    // lv_obj_t * list = lv_obj_create(lv_scr_act());
    lv_obj_t * list = lv_obj_create(bdwp_openapi_screen);//在data_handle_screen主屏幕创建
    // 设置 flex 布局，垂直排列
    lv_obj_set_flex_flow(list, LV_FLEX_FLOW_COLUMN);
    // 设置容器的滚动方向，LV_DIR_VER 表示“垂直方向可滚动”，如果内容超出容器高度，就可以上下滚动。
    lv_obj_set_scroll_dir(list, LV_DIR_VER);

    // 设置这个容器的宽和高
    lv_obj_set_size(list, DISP_WIDTH, DISP_HEIGHT - 40);
    //Q: ✅ lv_obj_align_to(obj1, obj2, LV_ALIGN_OUT_BOTTOM_MID, offsetx, offsety)
    // LV_ALIGN_OUT_BOTTOM_MID表示外部对齐，也就是obj1在obj2的外部然后进行对齐即obj1顶部中心点与obj2底部中心点进行对齐
    // 横向偏移offsetx px即obj1顶部中心点与obj2底部中心点横向偏移offsetx个像素点
    // 纵向偏移offsety即obj1顶部中心点与obj2底部中心点纵向偏移16px
    lv_obj_align_to(list, nav, LV_ALIGN_OUT_BOTTOM_MID, 0, 16);

    // 设置容器上下左右的内边距8像素，最后一个参数 0 是样式选择器，0 表示默认状态；
    lv_obj_set_style_pad_all(list, 8, 0);
    // 设置容器的背景颜色，最后一个参数0 是样式选择器，0表示默认状态。
    lv_obj_set_style_bg_color(list, lv_color_hex(0xfafafa), 0);

    lv_obj_set_style_pad_row(list, 8, 0);   // 子对象垂直间距
    for (int i = 0; i < ITEM_COUNT; i++) {
        /* 在容器list内创建一个 item 按钮对象 */
        lv_obj_t *item = lv_btn_create(list);
        //设置对象item的宽度为父容器的 100%；高度固定为 60 像素。
        lv_obj_set_size(item, lv_pct(100), 60);
        //背景色
        lv_obj_set_style_bg_color(item, lv_color_hex(0xffffff), 0);
        //圆角 8px
        lv_obj_set_style_radius(item, 8, 0);
        //内边距 10px
        lv_obj_set_style_pad_all(item, 10, 0);
        //阴影宽度 5px
        lv_obj_set_style_shadow_width(item, 5, 0);
        //透明度 20%
        lv_obj_set_style_shadow_opa(item, LV_OPA_20, 0);

        /* icon */
        //在按钮上创建一个图标标签
        lv_obj_t *icon = lv_label_create(item);
        //设置标题
        lv_label_set_text(icon, items[i].icon);
        lv_obj_set_style_text_font(icon, &single_color_emoji, LV_PART_MAIN);
        lv_obj_set_style_text_color(icon, lv_color_hex(0x128371), 0);
        //设置左边与父视图左中对齐
        lv_obj_align(icon, LV_ALIGN_LEFT_MID, 0, 0);

        /* label */
        //在按钮上创建一个图标标签
        lv_obj_t *label = lv_label_create(item);
        //设置标题
        lv_label_set_text(label, items[i].text);
        //设置该对象整体与icon对象的右边距离10px
        lv_obj_align_to(label, icon, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
        lv_obj_set_style_text_font(label, &lv_font_sans_sc_14, 0);
        //普通字符使用 lv_font_montserrat_20，字号 20。
        //中文字符则需要自己导入，此处使用自行导入的中文字符集chinese_font（需包含英文数字等常用字符）
        // lv_obj_set_style_text_font(label, &chinese_font, 0);
        //lv_obj_set_style_text_color设置字体颜色，lv_color_hex(0xfafafa)初始化颜色结构体lv_color_t
        lv_obj_set_style_text_color(label, lv_color_hex(0x128371), 0);

        /* 点击回调
        //Q: ✅C语言编译顺序问题：
        报错：❌error: use of undeclared identifier 'item_event_cb'
        108 | lv_obj_add_event_cb(item, item_event_cb, LV_EVENT_CLICKED, label);
        ✅原因分析：函数必须在使用前声明或定义即提前声明函数，否则编译器不知道这个函数的类型和参数，因此item_event_cb事件方法必须写在上面
        */
        //Q: int传参指针类型：最后一个参数是一个void *user_data指针，int值类型需采用(void *)转换成是一个指针类型，
        // 用intptr_t/uintptr_t可确保在32/64位平台上安全转换
        lv_obj_add_event_cb(item, item_event_cb, LV_EVENT_CLICKED, (void *)(intptr_t)i);

        /* 添加间距 */
        //给 list 容器的底部留 8 像素的间距；
        lv_obj_set_style_pad_bottom(list, 8, 0);
    }

    //Q: ✅调用lv_screen_load方法把这个bdwp_openapi_screen设置为当前活动屏幕替换掉原来的home_screen主屏幕而不是遮挡而已
    // 此时调用lv_scr_act()方法会返回该bdwp_openapi_screen
    // home_screen是一个指针，指向当前活动屏幕对象，被替换掉当前活动屏幕后不会变成NULL即不会变，
    // 也不会自动释放（除非手动调用lv_obj_del(home_screen)），它仍然指向原来的屏幕对象，只是这个屏幕不再是活动屏幕
    lv_screen_load(bdwp_openapi_screen);
}
